//-----------------------------------------------------------------------
// <copyright file="Update.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
// </copyright>
// <author>Andy Reeves</author>
// <email>andyr</email>
// <date>2004-03-23</date>
// <summary>Updates a versionnumber.exe.config file to the correct number</summary>
//-----------------------------------------------------------------------

namespace Microsoft.Sdc.Tasks.VersionNumber
{
    using System;
    using System.Xml;
    using System.Globalization;
    using System.IO;
    using Microsoft.Win32;

    using Microsoft.Build.Framework;

    /// <summary>
    /// Updates a versionnumber.exe.config file to the correct number
    /// </summary>
    /// <remarks>
    /// Note. This checks out the file in question from Source Control, updates the number and then checks the updated file back in.
    /// It only works with Source Depot currently.
    /// <code><![CDATA[
    /// <VersionNumber.Update
    ///             VersionNumberConfigFileLocation="versionNumberConfigFileLocation"
    ///             OnlyIncrementRevision="onlyIncrementRevision"
    ///             <Output TaskParameter="VersionNumber" ItemName="versionNumberItem"></Output>
    /// </VersionNumber.Update>
    /// ]]></code>
    /// <para>where:</para>
    /// <para><i>versionNumberConfigFileLocation (Required)</i></para>
    /// <para>Fully qualified file name to the xml config file. The file contains, in Xml format, the major, minor, build and revision number.</para>
    /// <para>
    /// For example:
    /// 
    /// <example>
    /// <code><![CDATA[
    /// <?xml version="1.0" encoding="utf-8"?>
    ///     <configuration>
    ///         <appSettings>
    ///             <add key="major" value="1" />
    ///             <add key="minor" value="2" />
    ///             <add key="build" value="1354" />
    ///             <add key="revision" value="155" />
    ///         </appSettings>
    ///     </configuration>
    /// ]]></code>    
    /// </example>
    /// </para>
    /// <para><i>onlyIncremementRevision</i></para>
    /// <para>Force Revision only change. So 2.0.1188.14 would be 2.0.1188.15 on the next automatic label from the source control system.</para>
    /// </remarks>
    /// <example>
    /// <code><![CDATA[
    /// <Project>
    ///     <Target Name="Test" >
    ///         <VersionNumber.Update VersionNumberConfigFileLocation="@(VersionXmlFile)">
    ///             <Output TaskParameter="VersionNumber" ItemName="VersionNumber" />
    ///         </VersionNumber.Update>
    ///     </Target>
    /// </Project>
    /// ]]></code>    
    /// </example>

    public class Update : TaskBase
    {
        private string buildNumberFull;
        private string versionNumberConfigFileLocation;
        private string versionNumberFormat; 
        private DateTime startDate = new DateTime(2000, 1, 1);
        private string buildBuildNumber;
        private bool onlyIncrementRevision;
        private string revisionBuildNumber;
        private int majorBuildNumber;
        private int minorBuildNumber;
        private bool skipSourceControl = false;

        private XmlDocument versionConfig = new XmlDocument();

        /// <summary>
        /// Initializes a new instance of the UpdateVersionNumber class.
        /// </summary>
        public Update()
        {
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <value>TODO</value>
        [Output]
        public string VersionNumber
        {
            get
            {
                return (buildNumberFull == null ? String.Empty : buildNumberFull);

            }
            set
            {
                //Setter must be here but don't do anything with the value passed in
            }
        }

        public string VersionNumberConfigFileLocation
        {
            get
            {
                return (this.versionNumberConfigFileLocation == null ? String.Empty : this.versionNumberConfigFileLocation);
            }
            set
            {
                this.versionNumberConfigFileLocation = value;
            }
        }

        /// <summary>
        /// If TRUE doesn't checkout or submit to source control
        /// </summary>
        /// <value></value>
        public bool SkipSourceControl
        {
            get { return this.skipSourceControl; }
            set { this.skipSourceControl = value; }
        }

        /// <summary>
        /// If TRUE only the revision part of the build number will be incremented
        /// </summary>
        /// <value>Force Revision only change. So 2.0.1188.14 would be 2.0.1188.15 on the next automatic label from the source control system.</value>
        public bool OnlyIncrementRevision
        {
            get
            {
                return this.onlyIncrementRevision;
            }
            set
            {
                this.onlyIncrementRevision = value;
            }
        }      

        /// <summary>
        /// If specified then this values indicates how the build number is calculated. i.e. a format of yyMMdd on July 15th ,2004 would give 040715. Note, this must only contain valid date formatting characters.
        /// If not specified the the build number format is the number of days since startDate from the xmlFile. If startDate is not specified then it defaults to Jan 1st, 2000.
        /// </summary>
        /// <value></value>
        public string VersionNumberFormat
        {
            get
            {
                return (this.versionNumberFormat == null ? String.Empty : this.versionNumberFormat);
            }

            set
            {
                versionNumberFormat = value;
            }
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <summary>
        /// Performs the action of this task.
        /// </summary>
        protected override void InternalExecute()
        {
            if (!this.SkipSourceControl)
            {
                Utilities.SdCmd("edit " + this.versionNumberConfigFileLocation, "AUTO Checking out the VersionNumber file...");
            }
            this.IncrementVersionNumbers();
            if (!this.SkipSourceControl)
            {
                Utilities.SdCmd("submit -C \"AUTO " + this.buildNumberFull + " revising build number file\"", "Updating build number...");
            }
        }

        private string CalculateNewBuildNumber()
        {
            string newBuild;

            if (this.versionNumberFormat == null || this.versionNumberFormat.Length == 0)
            {
                newBuild = Convert.ToString((DateTime.Now - this.startDate).Days);
            }
            else
            {
                newBuild = DateTime.Today.ToString(this.versionNumberFormat);
            }
            return newBuild;
        }
        /// <summary>
        /// TODO
        /// </summary>
        private void IncrementVersionNumbers()
        {
            this.LoadVersionNumber();

            string newBuild = CalculateNewBuildNumber();
            if ( (Convert.ToInt32(newBuild) > Convert.ToInt32(this.buildBuildNumber)) && !this.onlyIncrementRevision)
            {
                this.buildBuildNumber = newBuild;
                int revisionLength = this.revisionBuildNumber.Length;
                this.revisionBuildNumber = "0".PadLeft(revisionLength, '0');
            }
            else
            {
                int revision = Convert.ToInt32(this.revisionBuildNumber);
                revision++;
                int revisionLength = this.revisionBuildNumber.Length;
                this.revisionBuildNumber = Convert.ToString(revision).PadLeft(revisionLength, '0');
            }

            this.SaveVersionNumber();
        }

        /// <summary>
        /// TODO
        /// </summary>
        private void LoadVersionNumber()
        {
            this.versionConfig.Load(this.versionNumberConfigFileLocation);

            XmlNode majorNode = this.versionConfig.SelectSingleNode("configuration/appSettings/add[@key='major']");
            if (majorNode != null)
            {
                this.majorBuildNumber = XmlConvert.ToInt32(majorNode.Attributes["value"].Value);
            }

            XmlNode minorNode = this.versionConfig.SelectSingleNode("configuration/appSettings/add[@key='minor']");
            if (minorNode != null)
            {
                this.minorBuildNumber = XmlConvert.ToInt32(minorNode.Attributes["value"].Value);
            }

            XmlNode buildNode = this.versionConfig.SelectSingleNode("configuration/appSettings/add[@key='build']");
            if (buildNode != null)
            {
                this.buildBuildNumber = buildNode.Attributes["value"].Value;
            }

            XmlNode revisionNode = this.versionConfig.SelectSingleNode("configuration/appSettings/add[@key='revision']");
            if (revisionNode != null)
            {
                this.revisionBuildNumber = revisionNode.Attributes["value"].Value;
            }

            XmlNode startDateNode = this.versionConfig.SelectSingleNode("configuration/appSettings/add[@key='startDate']");
            if (startDateNode != null)
            {
                this.startDate = XmlConvert.ToDateTime(startDateNode.Attributes["value"].Value,"yyyy-MM-dd");
            }

            this.buildNumberFull = String.Format(CultureInfo.InvariantCulture, "{0}.{1}.{2}.{3}", this.majorBuildNumber, this.minorBuildNumber, this.buildBuildNumber, this.revisionBuildNumber);
        }

        /// <summary>
        /// TODO
        /// </summary>
        private void SaveVersionNumber()
        {
            XmlNode majorNode = this.versionConfig.SelectSingleNode("configuration/appSettings/add[@key='major']");
            XmlNode minorNode = this.versionConfig.SelectSingleNode("configuration/appSettings/add[@key='minor']");
            XmlNode buildNode = this.versionConfig.SelectSingleNode("configuration/appSettings/add[@key='build']");
            XmlNode revisionNode = this.versionConfig.SelectSingleNode("configuration/appSettings/add[@key='revision']");

            majorNode.Attributes["value"].InnerText = XmlConvert.ToString(this.majorBuildNumber);
            minorNode.Attributes["value"].InnerText = XmlConvert.ToString(this.minorBuildNumber);
            buildNode.Attributes["value"].InnerText = this.buildBuildNumber;
            revisionNode.Attributes["value"].InnerText = this.revisionBuildNumber;

            this.buildNumberFull = String.Format(CultureInfo.InvariantCulture, "{0}.{1}.{2}.{3}", this.majorBuildNumber, this.minorBuildNumber, this.buildBuildNumber, this.revisionBuildNumber);
            this.versionConfig.Save(this.versionNumberConfigFileLocation);
        }        
    }
}